<!--
    by: yuanbiao 2025年09月02日
    name: x-curved-text-2
    notes:
-->
<template>
  <svg :class="[prefixCls]" :viewBox="`0 0 ${width} ${height}`" width="100%" height="100%">
    <g>
      <path :id="id" :class="`${prefixCls}__path`" :d="path_d" />
      <text :class="[`${prefixCls}__text`, {[`${prefixCls}__text_ban`]: pointActiveIndex}]"
        dominant-baseline="middle" text-anchor="start" @click.stop="$emit('clickText')">
        <textPath :href="`#${id}`" startOffset="0%" method="align" spacing="auto" lengthAdjust="spacingAndGlyphs">
          <tspan v-for="(v,i) in wordList" :key="i" :data-i="i" :class="`${prefixCls}__word`"
            :style="v.style || {}" :dy="v.dy || 0" @click="$emit('clickWord',v,i)"
          >
            <template v-if="(v.letters || []).length">
              <tspan v-for="(v1,i1) in (v.letters || [])" :key="`${i}_${i1}`" :data-i="i1" :style="v1.style || {}"
                :class="`${prefixCls}__letter`" @click="$emit('clickLetter',v1,i1)"
              >{{ v1.text }}</tspan>
            </template>
            <template v-else>
              {{ v.text }}
            </template>
            <tspan :style="{'font-size': v.wordSpacing || 'inherit'}">&emsp;</tspan>
          </tspan>
        </textPath>
      </text>
      <template v-if="controllable">
        <template v-for="(v,i) in xPoints">
          <!-- 控制点 -->
          <circle :key="`circle_${i}`" :class="`${prefixCls}__control_point`"
            :cx="v.x" :cy="v.y" r="6"
            @touchstart.stop="touchstartHandler($event,i)"
            @mousedown.stop="mousedownHandler($event,i)"
          />
          <!-- 控制线 -->
          <line v-if="i > 0" :key="`line_${i}`" :class="`${prefixCls}__control_line`"
          :x1="xPoints[i-1].x" :y1="xPoints[i-1].y" :x2="v.x" :y2="v.y" />
        </template>
      </template>
    </g>
  </svg>
</template>

<script>
import { deepCopy } from '@/script/utils.js'
const prefixCls = 'x-curved-text'
let rawPoints = [
  { x: 100, y: 300, 'x%': 20, 'y%': 60, unit: 'px'},
  { x: 200, y: 200, 'x%': 40, 'y%': 40, unit: 'px'},
  { x: 300, y: 100, 'x%': 60, 'y%': 20, unit: 'px'},
]

export default {
  name: 'x-curved-text-2',
  props: {
    width: { type: Number, default: 500 },
    height: { type: Number, default: 500 },
    pointsTotal: { type: Number, default: 0 },
    points: { type: Array, default () {
      return []
    } },
    wordList: { type: Array, default () {
      return [
        // {
        //   text: 'hello',
        //   dy: '-0.5em',
        //   wordSpacing: 10,
        //   style: {
        //     fill: 'red', fontSize: 20, fontFamily: 'Arial', fontWeight: 'bold', fontStyle: 'italic',
        //   },
        //   letters: [
        //     {
        //       text: 'hello',
        //       style: {
        //         fill: 'green', fontSize: 20, fontFamily: 'Arial', fontWeight: 'bold', fontStyle: 'italic',
        //       }
        //     },
        //   ]
        // }
      ]
    } },
    controllable: { type: Boolean, default: true },
  },
  data() {
    return {
      prefixCls: prefixCls,
      id: `xPath_${Math.random().toString().substring(2)}`,
      xPoints: [],
      pointActiveIndex: '',
      dragInfo: { x_: 0, y_: 0, x0: 0, y0: 0, x1: 0, y1: 0 },
    }
  },
  computed: {
    mark () {
      return this.getMark(this.xPoints)
    },
    path_d () {
      let result = ''
      let len = this.xPoints.length
      let letter = 'S'
      if (len <= 3) {
        letter = 'Q'
      } else if (len === 4) {
        letter = 'C'
      }
      this.xPoints.forEach((v, i) => {
        if (i === 0) {
          result += `M${v.x},${v.y}`
        } else if (i === 1) {
          result += ` ${letter}${v.x},${v.y}`
        } else {
          result += ` ${v.x},${v.y}`
        }
      })
      return result
    },
  },
  watch: {
    pointsTotal: {
      handler (n) {
        if (n >= 3 && this.points.length === 0) {
          this.createPoints(n)
        }
      },
      immediate: true
    },
    points: {
      handler (n) {
        let _n = n || []
        if (this.pointsTotal >= 3 && _n.length > this.pointsTotal) {
          _n = _n.slice(0, this.pointsTotal)
        }
        let _mark = this.getMark(_n)
        if (_mark !== this.mark) {
          this.mergePoints(_n)
        }
      },
      immediate: true,
      deep: true
    },
    mark (n, o) {
      if (n !== o) {
        this.$emit('change', this.getPointInfo())
      }
    }
  },
  created () {
    if (this.xPoints.length === 0) {
      this.createPoints(this.pointsTotal)
    }
  },
  methods: {
    mergePoints (points) {
      if (points.length === 0) return
      let xPoints = deepCopy(rawPoints)
      points.forEach((v, i) => {
        xPoints[i] = { x: 0, y: 0, 'x%': 0, 'y%': 0, ...v }
        if (v.unit === '%') {
          xPoints[i]['x%'] = parseFloat(v.x) || 0
          xPoints[i]['y%'] = parseFloat(v.y) || 0
          xPoints[i].x = this.per2px(v.x, this.width)
          xPoints[i].y = this.per2px(v.y, this.height)
        } else {
          xPoints[i].x = parseFloat(v.x) || 0
          xPoints[i].y = parseFloat(v.y) || 0
          xPoints[i]['x%'] = this.px2per(v.x, this.width)
          xPoints[i]['y%'] = this.px2per(v.y, this.height)
        }
      })
      this.xPoints = xPoints
    },
    // 开始拖动
    touchstartHandler (e, pointIndex) {
      this.pointActiveIndex = pointIndex
      this.dragInfo.x1 = this.dragInfo.x0 = e.touches[0].clientX
      this.dragInfo.y1 = this.dragInfo.y0 = e.touches[0].clientY
      this.operationBefore()
      window.addEventListener('touchmove', this.touchmoveHandler)
      window.addEventListener('touchend', this.touchendHandler)
    },
    mousedownHandler (e, pointIndex) {
      this.pointActiveIndex = pointIndex
      this.dragInfo.x1 = this.dragInfo.x0 = e.clientX
      this.dragInfo.y1 = this.dragInfo.y0 = e.clientY
      this.operationBefore()
      window.addEventListener('mousemove', this.mousemoveHandler)
      window.addEventListener('mouseup', this.mouseupHandler)
      window.addEventListener('mouseleave', this.mouseupHandler)
    },
    // 拖动过程
    touchmoveHandler (e) {
      if (this.pointActiveIndex && e.touches[0]) {
        this.dragInfo.x1 = e.touches[0].clientX
        this.dragInfo.y1 = e.touches[0].clientY
        this.operationProcess()
      }
    },
    mousemoveHandler (e) {
      if (this.pointActiveIndex) {
        this.dragInfo.x1 = e.clientX
        this.dragInfo.y1 = e.clientY
        this.operationProcess()
      }
    },
    // 停止拖动
    touchendHandler () {
      this.operationAfter()
      this.pointActiveIndex = ''
      window.removeEventListener('touchmove', this.touchmoveHandler)
      window.removeEventListener('touchend', this.touchendHandler)
    },
    mouseupHandler () {
      this.operationAfter()
      this.pointActiveIndex = ''
      window.removeEventListener('mousemove', this.mousemoveHandler)
      window.removeEventListener('mouseup', this.mouseupHandler)
      window.removeEventListener('mouseleave', this.mouseupHandler)
    },
    operationBefore () {
      let item = this.xPoints[this.pointActiveIndex]
      this.dragInfo.x_ = item.x
      this.dragInfo.y_ = item.y
    },
    operationProcess () {
      let dx = this.dragInfo.x1 - this.dragInfo.x0
      let dy = this.dragInfo.y1 - this.dragInfo.y0
      let vx = this.dragInfo.x_ + dx
      let vy = this.dragInfo.y_ + dy
      this.$set(this.xPoints[this.pointActiveIndex], 'x', vx)
      this.$set(this.xPoints[this.pointActiveIndex], 'y', vy)
      this.$set(this.xPoints[this.pointActiveIndex], 'x%', this.px2per(vx, this.width))
      this.$set(this.xPoints[this.pointActiveIndex], 'y%', this.px2per(vy, this.height))
    },
    operationAfter () {
      if (this.pointActiveIndex) {
        let dx = this.dragInfo.x1 - this.dragInfo.x0
        let dy = this.dragInfo.y1 - this.dragInfo.y0
        let d = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
        if (d < 5) {
          this.$emit('tap', this.getPointInfo())
        }
      }
    },
    createPoints (n) {
      n = Math.max(parseInt(n) || 3, 3)
      let xPoints = [...this.xPoints]
      while (xPoints.length < n) {
        let _r = parseFloat((xPoints.length / n * 100).toFixed(2))
        xPoints.push({
          x: this.per2px(_r, this.width),
          y: this.per2px(_r, this.height),
          'x%': _r,
          'y%': _r,
          unit: 'px'
        })
      }
      // while (xPoints.length < n) {
      //   let _x = random(0, this.width)
      //   let _y = random(0, this.height)
      //   xPoints.push({
      //     x: _x,
      //     y: _y,
      //     'x%': this.px2per(_x, this.width),
      //     'y%': this.px2per(_y, this.height),
      //     unit: 'px'
      //   })
      // }
      this.xPoints = xPoints.slice(0, n)
    },
    getPointInfo () {
      let xPoints = deepCopy(this.xPoints)
      // 修正因width和height变化导致百分比变化
      xPoints.forEach(v => {
        if (v.unit === '%') {
          v.x = this.per2px(v['x%'], this.width)
          v.y = this.per2px(v['y%'], this.height)
        } else {
          v['x%'] = this.px2per(v.x, this.width)
          v['y%'] = this.px2per(v.y, this.height)
        }
      })
      this.xPoints = xPoints
      return { width: this.width, height: this.height, points: xPoints }
    },
    getMark (data) {
      data = data || []
      if (data.length === 0) return ''
      let arr = []
      if (data[0].unit === '%') {
        arr.push(this.width, this.height)
      }
      data.forEach(v => {
        if (v.unit === '%') {
          arr.push(v['x%'], v['y%'])
        } else {
          arr.push(v.x, v.y)
        }
      })
      return arr.join('_')
    },
    px2per (px, total) {
      let result = 0
      px = parseFloat(px)
      if (!isNaN(px)) {
        result = ((px / total || 0) * 100).toFixed(1)
      }
      return parseFloat(result)
    },
    per2px (per, total) {
      per = parseFloat(per) || 0
      total = parseFloat(total) || 0
      return total * per / 100
    },
  }
}
</script>
